﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MahonyAHRS  {

    public float[] Quaternion { get; set; }
    float twoKi;        // 2 * integral gain (Ki)
    float twoKpDef;
    float q0, q1, q2, q3;   // quaternion of sensor frame relative to auxiliary frame
    float integralFBx, integralFBy, integralFBz;  // integral error terms scaled by Ki
    float invSampleFreq;
    float roll, pitch, yaw;
    public void Mahony_Init() {
        twoKi = 0.5f * 1.0f;   // 2 * integral gain (Ki)
        twoKpDef = 120.0f * 0.5f;
        q0 = 1.0f;
        q1 = 0.0f;
        q2 = 0.0f;
        q3 = 0.0f;
        integralFBx = 0.0f;
        integralFBy = 0.0f;
        integralFBz = 0.0f;
        invSampleFreq = 1.0f / 150f;
        Quaternion = new float[] { 1f, 0f, 0f, 0f };
    }
    public void Mahony_update(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) {
        float recipNorm;
        float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
        float hx, hy, bx, bz;
        float halfvx, halfvy, halfvz, halfwx, halfwy, halfwz;
        float halfex, halfey, halfez;
        float qa, qb, qc;
        // Convert gyroscope degrees/sec to radians/sec
        gx *= 0.0174533f;
        gy *= 0.0174533f;
        gz *= 0.0174533f;

        // Compute feedback only if accelerometer measurement valid
        // (avoids NaN in accelerometer normalisation)
        if (!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

            // Normalise accelerometer measurement
            recipNorm = 1f/(float)Math.Sqrt(ax * ax + ay * ay + az * az);
            ax *= recipNorm;
            ay *= recipNorm;
            az *= recipNorm;

            // Normalise magnetometer measurement
            recipNorm = 1f / (float)Math.Sqrt(mx * mx + my * my + mz * mz);
            mx *= recipNorm;
            my *= recipNorm;
            mz *= recipNorm;

            // Auxiliary variables to avoid repeated arithmetic
            q0q0 = q0 * q0;
            q0q1 = q0 * q1;
            q0q2 = q0 * q2;
            q0q3 = q0 * q3;
            q1q1 = q1 * q1;
            q1q2 = q1 * q2;
            q1q3 = q1 * q3;
            q2q2 = q2 * q2;
            q2q3 = q2 * q3;
            q3q3 = q3 * q3;

            // Reference direction of Earth's magnetic field
            hx = 2.0f * (mx * (0.5f - q2q2 - q3q3) + my * (q1q2 - q0q3) + mz * (q1q3 + q0q2));
            hy = 2.0f * (mx * (q1q2 + q0q3) + my * (0.5f - q1q1 - q3q3) + mz * (q2q3 - q0q1));
            bx = (float)Math.Sqrt(hx * hx + hy * hy);
            bz = 2.0f * (mx * (q1q3 - q0q2) + my * (q2q3 + q0q1) + mz * (0.5f - q1q1 - q2q2));

            // Estimated direction of gravity and magnetic field
            halfvx = q1q3 - q0q2;
            halfvy = q0q1 + q2q3;
            halfvz = q0q0 - 0.5f + q3q3;
            halfwx = bx * (0.5f - q2q2 - q3q3) + bz * (q1q3 - q0q2);
            halfwy = bx * (q1q2 - q0q3) + bz * (q0q1 + q2q3);
            halfwz = bx * (q0q2 + q1q3) + bz * (0.5f - q1q1 - q2q2);

            // Error is sum of cross product between estimated direction
            // and measured direction of field vectors
            halfex = (ay * halfvz - az * halfvy) + (my * halfwz - mz * halfwy);
            halfey = (az * halfvx - ax * halfvz) + (mz * halfwx - mx * halfwz);
            halfez = (ax * halfvy - ay * halfvx) + (mx * halfwy - my * halfwx);

            // Compute and apply integral feedback if enabled
            if (twoKi > 0.0f) {
                // integral error scaled by Ki
                integralFBx += twoKi * halfex * invSampleFreq;
                integralFBy += twoKi * halfey * invSampleFreq;
                integralFBz += twoKi * halfez * invSampleFreq;
                gx += integralFBx;  // apply integral feedback
                gy += integralFBy;
                gz += integralFBz;
            } else {
                integralFBx = 0.0f; // prevent integral windup
                integralFBy = 0.0f;
                integralFBz = 0.0f;
            }

            // Apply proportional feedback
            gx += twoKpDef * halfex;
            gy += twoKpDef * halfey;
            gz += twoKpDef * halfez;
        }

        // Integrate rate of change of quaternion
        gx *= (0.5f * invSampleFreq);       // pre-multiply common factors
        gy *= (0.5f * invSampleFreq);
        gz *= (0.5f * invSampleFreq);
        qa = q0;
        qb = q1;
        qc = q2;
        q0 += (-qb * gx - qc * gy - q3 * gz);
        q1 += (qa * gx + qc * gz - q3 * gy);
        q2 += (qa * gy - qb * gz + q3 * gx);
        q3 += (qa * gz + qb * gy - qc * gx);

        // Normalise quaternion
        recipNorm = 1/(float)Math.Sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
        q0 *= recipNorm;
        q1 *= recipNorm;
        q2 *= recipNorm;
        q3 *= recipNorm;
        Quaternion[0] = q0 ;
        Quaternion[1] = q1 ;
        Quaternion[2] = q2;
        Quaternion[3] = q3;
    }
    void Mahony_computeAngles() {
        roll = (float)Math.Atan2(q0 * q1 + q2 * q3, 0.5f - q1 * q1 - q2 * q2);
        pitch = (float)Math.Asin(-2.0f * (q1 * q3 - q0 * q2));
        yaw = (float)Math.Atan2(q1 * q2 + q0 * q3, 0.5f - q2 * q2 - q3 * q3);
    }
    public float getRoll() {
         Mahony_computeAngles();
        return roll * 57.29578f;
    }
    public float getPitch() {
         Mahony_computeAngles();
        return pitch * 57.29578f;
    }
    public float getYaw() {
         Mahony_computeAngles();
        return yaw * 57.29578f + 180.0f;
    }
}
